Blake Meike Marakana.com
Developer, Architect, Android Evangelist
blake.meike@gmail.com
twitter: @callmeike
blog: http://portabledroid.wordpress.com/
I think you’ll get the most out of this if:
These are just a few of the hundreds of articles that turn up in response to a query for "enterprise mobile"
The usually cited risks are:
As developers, I think we face one more issue:
Web service developers and mobile developers have different understandings of what the environment looks like.
Actually, an Android application looks a lot like a web application:
I’d like to talk in some detail about a couple of practices that may seem wrong at first, to one camp or the other (possibly to both).
Embrace them, though, and you will create better, simpler, and more reliable Enterprise Applications
When a Web Service Developer has to deal with a database, the tool of choice is very likely to be some kind of Object-Relational Mapping framework:
Android ORM frameworks do exist:
They might even be a good idea, in some applications…
In Android, forget ORM: Just use Cursors
One possibility: a lightweight bean-like accessor.
Very simple!
public class EACursorAccess extends CursorAccess { public EACursorAccess(Cursor c) { super(c, EAContract.Columns.ID, EAContract.Columns.COL1, // other columns... EAContract.Columns.COLN); } public int getId() { return getCursor().getInt(getColIdx(EAContract.Columns.ID)); } public String getCol1() { return getCursor().getString(getColIdx(EAContract.Columns.COL1)); } // Other getters... public String getColN() { return getCursor().getString(getColIdx(EAContract.Columns.COLN)); } }
ValuesCursorAccess pojo = new ValuesCursorAccess(cursor); while (cursor.moveToNext()) { // most pojo operations are safe here... processPOJO(pojo); }
public abstract class CursorAccess { private final Cursor cursor; private final Map<String, Integer> colMap; public CursorAccess(Cursor c, String... cols) { cursor = c; Map<String, Integer> m = new HashMap<String, Integer>(); for (String col: cols) { int idx = c.getColumnIndex(col); if (0 <= idx) { m.put(col, Integer.valueOf(idx)); } } colMap = Collections.unmodifiableMap(m); } protected final Cursor getCursor() { return cursor; } protected final int getColIdx(String col) { Integer idx = colMap.get(col); if (null == idx) { throw new IllegalArgumentException( "Cursor does not contain column: " + col); } return idx.intValue(); } }
Instead of abstracting cursors away in Android,
use them, pervasively, as a standard abstraction for data
Next up: Networks are data…
If you are embarking on a connected application, you absolutely should view Virgil Dobjanschi’s 2010 Google I/O talk:
http://www.youtube.com/watch?v=xHXn3Kg2IQE
The architecture he proposes probably seems excessively complex…
In Android, a network connection should be fronted by a ContentProvider
The “Figure Eight”:
Losing AsyncTasks is a major win. They are a constant headache!
Although it is, architecturally, a separate component, it is really quite simple.
Possible implementations include:
Here’s a simple implementation using a PendingIntent:
public void doSomething(Activity ctxt, String arg1, String argn) { Intent intent = new Intent(ctxt, EnterpriseService.class); intent.putExtra(EnterpriseService.ARG1, arg1); // add other args... intent.putExtra(EnterpriseService.ARGN, argn); intent.putExtra( EnterpriseService.CALLBACK, ctxt.createPendingResult( REQ_ID, new Intent(), // empty default response PendingIntent.FLAG_ONE_SHOT)); ctxt.startService(intent); }
protected void onHandleIntent(Intent intent) { Intent resp = new Intent(); resp.putExtra( RESULT, doSomething( intent.getExtras().getString(ARG1), // other args... // intent.getParcelableExtra(...) intent.getExtras().getString(ARGN))); try { ((PendingIntent) intent.getParcelableExtra(CALLBACK)) .send(this, Activity.RESULT_OK, resp); } catch (CanceledException e) { Log.w(TAG, "Cancelled!", e); } }
Called from Activity.onActivityResult:
public boolean onDoSomethingResult( int reqCode, int resCode, Intent resp, MessageHandler hdlr) { if (REQ_ID != reqCode) { return false; } if (Activity.RESULT_OK != resCode) { hdlr.onFail(resCode); } else { hdlr.onMessage(resp.getExtras().getString(EnterpriseService.RESULT)); } return true; }
The Processor is probably the most complex Component. This is complexity that used to be distributed across your AsyncTasks.
Apache HTTP client has fewer bugs in Android 2.2 (Froyo) and earlier releases. For Android 2.3 (Gingerbread) and later, HttpURLConnection is the best choice.
AsyncTasks hide state transitions in ephemeral objects.
Just don’t use them for network transactions.
Next up: The right place to record state transitions
When a Web Service Developer has to deal with a database they probably think DAO.
The Android Documentation even states that, “unless you intend to share your data, you may not need a ContentProvider”
In Android, a ContentProvider is the DAO. Just use it.
I admit that this case is harder to make than the previous two.
The virtual tables that your Content Provider publishes should not be the real tables or real columns. The QueryBuilder supports this separation.
Note that the EXTRA column is completely virtual…
private static final Map<String, String> COL_AS_MAP; static { Map<String, String> m = new HashMap<String, String>(); m.put(EAContract.Columns.ID, DbHelper.COL_ID + " AS " + EAContract.Columns.ID); m.put(EAContract.Columns.COL1, DbHelper.COL_A + " AS " + EAContract.Columns.COL1); m.put(EAContract.Columns.COLN, "CASE WHEN " + DbHelper.COL_Z + " NOT NULL THEN " + DbHelper.COL_ID + " ELSE NULL END AS " + EAContract.Columns.COLN); COL_AS_MAP = Collections.unmodifiableMap(m); }
The Query method gets really (safe and) simple.
private Cursor doQuery( String[] proj, String sel, String[] selArgs, String ord, long pk) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setStrict(true); qb.setProjectionMap(COL_AS_MAP); qb.setTables(DbHelper.TABLE); if (0 <= pk) { qb.appendWhere(PK_CONSTRAINT + pk); } return qb.query( helper.getWritableDatabase(), proj, sel, selArgs, null, null, ord); }
Android thinks of data in terms of Content Providers
If you think that way too, your life will be easier
What would I like you to take away from this?
Slides and code at:
https://github.com/bmeike/AnDevConIV.git
/
#